/**
 * 
 */
package it.uniroma3.dia.digraph.view.interpolation.impl;

import it.uniroma3.dia.digraph.algorithm.utility.Utility;
import it.uniroma3.dia.digraph.view.DrawingPanel;
import it.uniroma3.dia.digraph.view.Finestrella;
import it.uniroma3.dia.digraph.view.interpolation.Interpolator;
import it.uniroma3.dia.digraph.view.interpolation.NodeView;

import java.util.HashMap;
import java.util.Map;

import org.graphdrawing.graphml.xmlns.GraphType;
import org.graphdrawing.graphml.xmlns.NodeType;

/**
 * A linear interpolator
 * @author BarycentricCoords
 *
 */
public class LinearInterpolator implements Interpolator {

	private DrawingPanel drawingPanel;

	protected double step;
	private long pause;
	private long durationMillis;
	private int frameNumber;

	protected GraphType oldGraph;

	protected Map<String, NodeView> oldNodePositions;
	protected Map<String, NodeView> currentNodePositions;
	protected Map<String, NodeView> destNodePositions;

	/**
	 * Creates a LinearInterpolator with default values
	 */
	public LinearInterpolator(){
		this(new DrawingPanel());
	}
	
	/**
	 * Creates a LinearInterpolator with the specified drawing panel
	 * 
	 * @param drawingPanel
	 */
	public LinearInterpolator(DrawingPanel drawingPanel) {
		this(drawingPanel, 1000);
	}

	/**
	 * @param drawingPanel
	 * @param frameNumber
	 */
	public LinearInterpolator(DrawingPanel drawingPanel, int frameNumber) {
		this(drawingPanel, frameNumber, 1000);
	}

	/**
	 * @param drawingPanel
	 * @param frameNumber
	 * @param durationMillis
	 */
	public LinearInterpolator(DrawingPanel drawingPanel, int frameNumber,
			long durationMillis) {

		this.drawingPanel = drawingPanel;
		this.step = 1.0 / frameNumber;
		this.pause = (long) (durationMillis / frameNumber);
		this.durationMillis = durationMillis;
		this.frameNumber = frameNumber;

		this.oldGraph = null;
		this.oldNodePositions = null;
		this.currentNodePositions = null;
		this.destNodePositions = null;
		
		
	}

	/**
	 * @return the drawingPanel
	 */
	public DrawingPanel getDrawingPanel() {
		return drawingPanel;
	}

	/**
	 * @param drawingPanel
	 *            the drawingPanel to set
	 */
	public void setDrawingPanel(DrawingPanel panel) {
		this.drawingPanel = panel;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniroma3.dia.digraph.view.interpolation.impl.Interpolator#interpolate
	 * (org.graphdrawing.graphml.xmlns.GraphType)
	 */
	public void interpolate(GraphType dest) {
		this.oldGraph = dest;
		double t;
		Map<String, NodeType> nodeMap = Utility.extractNodes(dest);
		currentNodePositions = this.extractNodes(dest);
		destNodePositions = this.extractNodes(dest);

		if (this.oldNodePositions != null) {
			currentNodePositions = this.clone(oldNodePositions);
			
			for (NodeType n :nodeMap.values()){
				try {
					currentNodePositions.get(n.getId()).setDefault(n.isDefault());
				}
				catch (NullPointerException npe){}
			}

			for (t = 0; t < 1 - step; t += step) {
				updateNodesPositions(t);
				notifyPanel();
			}
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.oldGraph = dest;
		this.oldNodePositions = this.extractNodes(dest);

		notifyPanel();
//		Finestrella.getChar();
	}

	protected Map<String, NodeView> clone(Map<String, NodeView> toClone) {
		Map<String, NodeView> result = new HashMap<String, NodeView>();
		for (String id : toClone.keySet())
			result.put(id, new NodeView(toClone.get(id)));
		return result;
	}

	protected void notifyPanel() {
		drawingPanel.setId2nodes(currentNodePositions);
		drawingPanel.setEdges(this.oldGraph.getEdgeCollection().getEdge());
		try {
			Thread.sleep(pause);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		drawingPanel.repaint();

	}

	protected Map<String, NodeView> extractNodes(GraphType graph) {
		Map<String, NodeView> id2nodes = new HashMap<String, NodeView>();

		for (NodeType node : graph.getNodeCollection().getNode())
			id2nodes.put(node.getId(), new NodeView(node));

		return id2nodes;
	}

	/**
	 * @param t
	 */
	protected void updateNodesPositions(double t) {
		int currentX;
		int currentY;
		double current1mT;
		NodeView currentNode;
		current1mT = 1 - t;

		// update node positions
		for (String currentNodeId : oldNodePositions.keySet()) {
			currentNode = currentNodePositions.get(currentNodeId);
			
			currentX = (int) (oldNodePositions.get(currentNodeId).getX()
					* current1mT + destNodePositions.get(currentNodeId).getX()
					* t);
			currentY = (int) (oldNodePositions.get(currentNodeId).getY()
					* current1mT + destNodePositions.get(currentNodeId).getY()
					* t);

			
			currentNode.setX(currentX);
			currentNode.setY(currentY);
		}
	}

	@Override
	public long getAnimationDuration() {
		return this.durationMillis;
	}

	@Override
	public int getFrameNumber() {

		return this.frameNumber;
	}

	@Override
	public void setAnimationDuration(long millis) {
		this.durationMillis = millis;

	}

	@Override
	public void setFrameNumber(int number) {
		this.frameNumber = number;

	}



}
